//	-*- Mode: Javascript; tab-width: 4; indent-tabs-mode: tab; c-basic-offset: 2 -*-
/*******************************************************************************
	(C)2012+ TriMoon Inc.
	Authors:
		Pappawolfie
		Charlie Ewing
		TriMoon <http://claimid.com/trimoon>

	This work is licensed under a Creative Commons License !
	See: http://creativecommons.org/licenses/by-nc-nd/3.0/

	See also:
		http://wiki.greasespot.net/Metadata_Block
		http://www.greywyvern.com/code/php/binary2base64
		http://www.loc.gov/standards/iso639-2/php/code_list.php
*******************************************************************************/
// ==UserScript==
// @name			Wall Manager3 Sidekick (CastleVille)
// @description		Assists Wall Manager with CastleVille posts
// @namespace		claimid.com/trimoon
// @version			3.2.0.0
// @updateURL		https://fbwm.googlecode.com/hg/sidekicks/CastleVille.user.js
// @downloadURL		https://fbwm.googlecode.com/hg/sidekicks/CastleVille.user.js
// @icon			https://fbwm.googlecode.com/hg/icons/CastleVille.png
// @include			/^https?:\/\/www\.facebook\.com\/pages\/FB-Wall-Manager\//
// @include			/^https?:\/\/apps\.facebook\.com\/playcastleville\/receive.php\?/
// @include			/^https?:\/\/.*\.castle\.zynga\.com\/receive.php\?/
// @require			../main/wmLibrary.js
// @grant			GM_log
// @grant			GM_getValue
// @grant			GM_setValue
// @grant			unsafeWindow
// @license			http://creativecommons.org/licenses/by-nc-nd/3.0/
// @copyright		contributors
// ==/UserScript==

/* Recent Changes
	Changes in the wmLibrary script (123889 included above) now allow for better communication with 
	the WM host. These changes will only work for users of WM 3 and above. If you intend to grant
	backward support, you may continue delivering status codes via window.top.location.hash or a
	forced redirect to something WM can see. The sidekick can now be flagged with "postMessageCompatible"
	which will cause WM 3's sidekick handler to only listen for WM3 compatible return values. Omitting
	that flag will cause the sidekick handler to only look for WM2 compatible location.hash values.
	I have commented the exact locations of these changes.
	
	The sample tests below have been updated to reflect form-2 tests introduced early in WM 2 during
	changes to the dynamic grabber. Form-2 is the preferred method for WM3, but there is nothing
	stopping you from using the shorter Form-1 search/find combinations. See the wiki for more details.

        The automatic updater script reference has been removed. Updating should now be handled by the script runner.
*/


(function() {
	//NEW for WM3! This line causes the top window to listen for commands from WM's sidekick handler
	if (window.top==window.self) Sidekick.listen();
	//clean up the handler channel when page is unloaded
	window.addEventListener("onBeforeUnload", Sidekick.unlisten, false);	

	//prevent reading data from top page because it does not contain useful information and can trick the sidekick
	//remove this line if the top page is actually the page you need to read from
	if ((window.top==window.self) && !window.location.href.match( /(^http:\/\/www\.facebook\.com\/pages\/FB-Wall-Manager\/)/ )) return; //comment this line out if you A) do not get your accept/fail messages from an iframe, or B) the game ticker does not affect you or draw to your collection pages.

	//predefine some variables so we can just call them when needed instead of typing the same stuff over and over again
	var version = "3.2.0.0"; //use something like 1.2.3.4 where 1 is the version, 2 is the major revision, 3 is the minor revision, and 4 is a bug fix
	var appID="107040076067341";
	var scriptID="<script id here>"; //the id of your script on userscripts.org
	var appName="CastleVille";
	var appIcon="https://fbwm.googlecode.com/hg/icons/CastleVille.png";
	var appThumbsSource="http://platform.ak.fbcdn.net/www/app_full_proxy.php?app=107040076067341";

	//attempt to dock with the WM host script
	function dock(){
	
		/*
			enter material names here as objects
			each object takes a name, id and event
			id defaults to name - spaces and lowercase
			event defaults to "Unsorted"
			flags defaults to null

			Use the following for:
				event:"x" - Listed under "x"
				flags:"get|send|both" - to set material for gets/sends/both, use only 1 !
		*/
		var materials = [
			// {name:"", id:"", event:"", flags:"get|send|both"},
			// General - event:"General"
			{name:"Kingdom Renamed(Coins)", id:"FeedKingdomRenamed", event:"General", flags:"get"},
			// Castle - id:"FeatCastle_Feed", event:"Castle"
			{name:"Crew Complete", id:"FeatCastle_FeedCrewComplete", event:"Castle", flags:"both"},
			{name:"Peacock Penthouse Complete", id:"FeatCastle_FeedPeacockPenthouseComplete", event:"Castle", flags:"both"},
			// CoreDoober - id:"CoreDoober_", event:"CoreDoober"
			{name:"Butter", id:"CoreDoober_Butter", event:"CoreDoober", flags:"get"},
			{name:"Chicken Meat", id:"CoreDoober_ChickenMeat", event:"CoreDoober", flags:"get"},
			{name:"Coal", id:"CoreDoober_Coal", event:"CoreDoober", flags:"both"},
			{name:"One Grain of Sand", id:"CoreDoober_Sand", event:"CoreDoober", flags:"both"},
			{name:"Sprinkles", id:"CoreDoober_Sprinkles", event:"CoreDoober", flags:"get"},
			// CoreNPC - id:"CoreNPC_Feed", event:"CoreNPC"
			// CoreNPC - id:"CoreNPC_", event:"CoreNPC", // no "Feed" part
			{name:"Boo Brew", id:"CoreNPC_FeedBooBrew", event:"CoreNPC", flags:"send"},
			{name:"Book Of Spirits", id:"CoreNPC_FeedBookOfSpirits", event:"CoreNPC", flags:"both"},
			{name:"Bubble(Yellow)", id:"CoreNPC_FeedBubble_Yellow", event:"CoreNPC", flags:"both"},
			{name:"Bubble(Red)", id:"CoreNPC_FeedBubble_Red", event:"CoreNPC", flags:"both"},
			{name:"Bushel of Turnips", id:"CoreNPC_FeedBushelOfTurnips", event:"CoreNPC", flags:"both"},
			{name:"Butterfly Cake", id:"CoreNPC_ButterflyCake", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Butterscotch", id:"CoreNPC_FeedSecretSauce", event:"CoreNPC", flags:"both"},
			{name:"Blue Ticket Piece", id:"CoreNPC_FeedTicketPieceBlue", event:"CoreNPC", flags:"both"},
			{name:"Charm", id:"CoreNPC_FeedCharmBracelet", event:"CoreNPC", flags:"both"},
			{name:"Clothes Pin", id:"CoreNPC_FeedClothespin_", event:"CoreNPC", flags:"both"},
			{name:"Conjuring Quill", id:"CoreNPC_FeedConjuringQuill", event:"CoreNPC", flags:"both"},
			{name:"Copper Trinket", id:"CoreNPC_FeedBrassNugget", event:"CoreNPC", flags:"send"},
			{name:"Dragon Tooth", id:"CoreNPC_FeedDragonTooth", event:"CoreNPC", flags:"both"},
			{name:"Flower Salad", id:"CoreNPC_FlowerSalad", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Fluffy Slippers", id:"CoreNPC_FeedFluffySlippers", event:"CoreNPC", flags:"both"},
			{name:"Gloom Sample", id:"CoreNPC_FeedGloomSample", event:"CoreNPC", flags:"both"},
			{name:"Harvest Gourds", id:"CoreNPC_FeedHarvestGourd", event:"CoreNPC", flags:"both"},
			{name:"Hot Coal", id:"CoreNPC_FeedHotCoal", event:"CoreNPC", flags:"both"},
			{name:"Imp Hair", id:"CoreNPC_FeedImpHair", event:"CoreNPC", flags:"both"},
			{name:"Liquid Rainbow", id:"CoreNPC_LiquidRainbow", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Magic Orb", id:"CoreNPC_FeedMagicOrb", event:"CoreNPC", flags:"both"},
			{name:"Party Masks", id:"CoreNPC_FeedPartyMasks", event:"CoreNPC", flags:"both"},
			{name:"Peacock Feathers", id:"CoreNPC_PeacockFeathers", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Pirate Food", id:"CoreNPC_FeedPirateFood", event:"CoreNPC", flags:"both"},
			{name:"Rose-Colored Glasses", id:"CoreNPC_RoseColoredGlasses", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Rusty Pipe", id:"CoreNPC_FeedRustyPipe", event:"CoreNPC", flags:"get"},
			{name:"Skeleton Hand", id:"CoreNPC_FeedSkeletonHand", event:"CoreNPC", flags:"both"},
			{name:"Skull", id:"CoreNPC_FeedSkull", event:"CoreNPC", flags:"both"},
			{name:"Spark Of Creativity", id:"CoreNPC_SparkOfCreativity", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Splash of Magic", id:"CoreNPC_SplashOfMagic", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Ticket Piece(Red)", id:"CoreNPC_FeedTicketPieceRed", event:"CoreNPC", flags:"both"},
			{name:"Treasure Hunters Kit", id:"CoreNPC_TreasureHuntersKit", event:"CoreNPC", flags:"both"}, // no "Feed" part
			{name:"Valve", id:"CoreNPC_FeedValve", event:"CoreNPC", flags:"both"},
			{name:"Wooden Disc", id:"CoreNPC_FeedWoodenDisc", event:"CoreNPC", flags:"both"},
			// CoreResource - id:"CoreResource_Feed", event:"CoreResource"
			{name:"Energy(Ask)", id:"CoreResource_FeedAskEnergy", event:"CoreResource", flags:"both"},
			// CoreVisit - id:"CoreVisit_Feed", event:"CoreVisit"
			{name:"Energy(Did Work)", id:"CoreVisit_FeedDidWork", event:"CoreVisit", flags:"both"},
			// Expansions - id:"CoreExpand_Feed", event:"Expansions"
			{name:"Crystal Shard(Ask)", id:"CoreExpand_FeedAsk", event:"Expansions", flags:"both"},
			{name:"Crystal Shard(Expansion Unlocked)", id:"CoreExpand_FeedExpansionUnlocked", event:"Expansions", flags:"both"},
			// Masteries - id:"Feat.*_FeedMasteryLevel_", event:"Masteries"
			{name:"Artistry Energy", id:"FeatArtistry_FeedMasteryLevel_", event:"Masteries", flags:"get"},
			{name:"Cooking XP", id:"FeatCooking_FeedMasteryLevel_", event:"Masteries", flags:"get"},
			{name:"Forging XP", id:"FeatForging_FeedMasteryLevel_", event:"Masteries", flags:"get"},
			// Parties - id:"Party", event:"Parties"
			{name:"Party End", id:"PartyEnd_Feed", event:"parties", flags:"both"},
			// Quests - id:"Quest_FeedDefault_", event:"Quests"
			{name:"Absorbent Cotton", id:"Quest_FeedDefault_cottonmaterial", event:"quests", flags:"get"},
			{name:"Crystal Shard", id:"Quest_FeedDefault_crystalshard", event:"quests", flags:"get"},
			{name:"Flower Box", id:"Quest_FeedDefault_flowerbox", event:"quests", flags:"get"},
			{name:"Hammer", id:"Quest_FeedDefault_hammer", event:"quests", flags:"both"},
			{name:"Honey", id:"Quest_FeedDefault_honey", event:"quests", flags:"get"},
			{name:"Parchment", id:"Quest_FeedDefault_parchment", event:"quests", flags:"get"},
			{name:"Popcorn", id:"Quest_FeedDefault_popcorn", event:"quests", flags:"get"},
			{name:"Red Tulip", id:"Quest_FeedDefault_redtulip", event:"quests", flags:"both"},
			{name:"Regal Hoe", id:"Quest_FeedDefault_regalhoe", event:"quests", flags:"get"},
			{name:"Reputation", id:"Quest_FeedDefault_reputation", event:"quests", flags:"get"},
			{name:"Spy Glass", id:"Quest_FeedDefault_spyglass", event:"quests", flags:"both"},
			{name:"Stone", id:"Quest_FeedDefault_stones", event:"quests", flags:"both"},
			{name:"Sugar", id:"Quest_FeedDefault_Sugar", event:"quests", flags:"get"},
			{name:"Three Carrots", id:"Quest_FeedDefault_Threecarrots", event:"quests", flags:"both"},
			{name:"Wood Club", id:"Quest_FeedDefault_woodclub", event:"quests", flags:"both"},
			{name:"XP", id:"Quest_FeedDefault_xp", event:"quests", flags:"both"},
			// Unicorns - id:"UnicornRaising", event:"Unicorns"
			{name:"Unicorn XP", id:"UnicornRaisingEnd_Feed", event:"unicorns", flags:"send"},
		];

		//mark all these as new while building the menus
		//these items will appear highlighted green in the options menu
		//this sidekick tutorial has but one color for new items. if you need more colors, ask for DDM's color handler to be cloned to your sidekick
		// One of the below doesn't work it seems atm. 10/oct/12
		var newItems=[
			"Unicorn XP",
			"sendunicornraisingendfeed",
		];

		//these words generally denote a send type bonus
		//modify this list to fit your needs
		//not all sidekicks will want to differentiate between send and get type bonuses, but I built this for FrontierVille
		//which had daily limits of two sorts so I needed to differentiate
		var sendWords = [
			// EN
			"needs","send","looking for","get one too","get some too","could sure use some","want to get","You'll get a","envíale","envoie-leur",
			// TR
			"gönder",
		];

		//manage accept texts here, which are true text names of your bonuses
		var accTextGet={}, accTextSend={}, accTextBoth={};
		//by default, this sidekick will have text for both sent and collected items by name
		accTextGet = accTextFromData(matchByParam(materials,"flags","get"));
		accTextSend = accTextFromData(matchByParam(materials,"flags","send"),"send");
		accTextBoth = accTextFromData(matchByParam(materials,"flags","both"),"send");
		
		//enter below anything you want special, something changed, or to correct text messed up by the game creators
		var accTextCustoms = {
			doUnknown:"Unknown", //needed if your script contains the menu item "doUnknown" (which is added by default now by WM host)
			send:"Unknown", //needed if your script returns just "send" without a material name and "sendall" is used in the menu
		};
		
		//get a list of search terms from the materials object. These terms are built specifically for text searches
		//there is no need to worry about case because the searchable portion and the search terms are all converted to lower case in the WM host
		var searchList = searchFromData(materials); //pulls the id||name field from the materials object above to use as a search term
		var searchListNoSpaces = searchList.noSpaces(); //this version contains no spaces in each element

		//manage tests here. tests are the actual search commands performed on post data to determine what they give
		//there are two forms by which you can make tests
		
		//Form1: {<search_where>:<find_what>, ret:<return_value>} 
		//		OR find multiple terms at once {<search_where>:[<find_what>,<find_what>], ret:<return_value>}
		//Form2: {search:<search_where>, find:<find_what>, ret:<return_value>} 
		//		OR find multiple terms at once from multiple places {search:[<search_where>,<search_where>,...], find:[<find_what>,<find_what>,...], ret:<return_value>}
		//See wiki for many more options
		var tests=[
			//if send words are found, but material does not exist in the list, returns just "send" so sendall picks it up
			//note that this is a tested test where subtests take presidence over return values given by the parent test
			//what that means is that if the inner tests fail, the outer test still returns "send" because something in sendWords[] was found
			{search:["link","body"], find:sendWords, ret:"send", kids:[
				//otherwise return the actual material name prefixed with "send". Example: "sendnailgun"
				{search:["url"], find:"{%1}", ret:"send{%1}", subTests:searchList}, //search first in the url
				{search:["link","body"], find:"{%1}", ret:"send{%1}", subTests:searchList}, //search second in the post link and body
			]},

			//check for getting of materials by id
			{search:["url"], find:"{%1}",ret:"{%1}",subTests:searchList}, //search first in the url
			{search:["link","body"], find:"{%1}",ret:"{%1}",subTests:searchList}, //search second in the post link and body

		];

		//manage your menu here
		//this menu is attached to the options menu already available in the WM host
		//future versions of WM may attach your menu segment in another location
		menu={
			//this is the section housing for your sidekick which displays its name and current version
			section_main:{type:"section",label:appName+" ("+version+")",kids:{

				//this links directly to your sidekick installer on userscripts.org
				updateSidekick:{type:'link',label:'Update Sidekick',href:'http://userscripts.org/scripts/source/'+scriptID+'.user.js'},

				doUnknown:{type:"checkbox",label:"Process Unknown Links"},
				send:{type:"checkbox",label:"Send Unrecognized Items"},
				sendall:{type:"checkbox",label:"Send All (or select from options below)"},

				// get/send/both sections
				sep_GET:{type:"separator",label:"Get Only",kids:{}},
				sep_SEND:{type:"separator",label:"Send Only",kids:{}},
				sep_BOTH:{type:"separator",label:"Get & Send",kids:{}},
			}},
		};

		//attach material lists to the menu
		//here we grab all the data out of the materials object (far) above and create a simple menu structure from it
		//automatic menu structure is in the form of GET/SEND > EVENT > NAME
		
		//use these to auto-fill your menus
		menuFromData(matchByParam(materials,"flags","get"),menu["section_main"]["kids"]["sep_GET"]["kids"],newItems,"",MENU_ID_ENFORCE_NAME);
		menuFromData(matchByParam(materials,"flags","send"),menu["section_main"]["kids"]["sep_SEND"]["kids"],newItems,"send",MENU_ID_ENFORCE_NAME);
		menuFromData(matchByParam(materials,"flags","both"),menu["section_main"]["kids"]["sep_BOTH"]["kids"],newItems,"send",MENU_ID_ENFORCE_NAME);
		
		//special: menuFromData automatically uses the id||name given in the materials object above, which assumes you are identifying
		//items with the same text they might be named as, allowing you to simply omit the id or name field above
		//however, if you identify an item with the word "fruit01" but its actually a "Blueberry", then your id may not be what you want
		//listed in your menu. In that case, you can force the menuFromData function to render the name field exclusively by using a flag
		//menuFromData(materials,menu["section_main"]["kids"]["sep_GET"]["kids"],newItems,"",MENU_ID_ENFORCE_NAME);

		//send all this information in to the WM script
		//this sidekick function relays all the information you provided here to the host script when 
		//this sidekick activates on the same page as the host
		//for details on specific parts, please see the wiki
		Sidekick.dock({
			appID:appID,
			name:appName, 
			version:version,
			thumbsSource:appThumbsSource,
//			thumbsSource:null,
			flags:{postMessageCompatible:true}, //this is required for WM3 sidekick handler and for use with Chrome
			alterLink:{},
			icon:appIcon,
			desc:null,
			accText:mergeJSON(accTextGet,accTextSend,accTextBoth,accTextCustoms), //joins automatically generated texts with your custom texts
			tests:tests,
			menu:menu,
		});
	};

	function read(){
		try{
			var statusCode=0, text=document.documentElement.textContent;
		} catch(e){window.setTimeout(read,1000);return;} 

		/* **************************************************
		Modify this section so it catches your exact
		accept or failure texts returned by the target
		app/game

		Anything goes, as long as a status code
		is returned to the main WM host.

		Defaults are in RegExp format, but you can 
		change to simple if/then/else or switches
		if you have something better in mind

		The WM script can recieve and act on the following statusCode values:

			"2":"Responseless Collection",			//normally handled inside WM host
			"1":"Accepted",
			"0":"Unknown",
			"-1":"Failed", 					//use for reason unknown
			"-2":"None Left", 				//use for reason of already taken
			"-3":"Over Limit",
			"-4":"Over Limit, Sent One Anyway",		//registers as a type of acceptance
			"-5":"Server Error",
			"-6":"Already Got",				//registers as a type of acceptance
			"-7":"Server Down For Repairs",
			"-8":"Problem Getting Passback Link", 		//used only with special flags
			"-9":"Final Request Returned Null Page", 	//normally handled inside WM host
			"-10":"Final Request Failure",			//normally handled inside WM host
			"-11":"Expired",
			"-12":"Not a Neighbor",				//neighborship required to accept post
			"-13":"Requirements not met",			//level or building requirement
			"-15":"Unrecognized Response",			//prevent waiting for timeout if you know to watch for some known issue

			additional codes may now exist, please check the wiki support site for information
			
			NEW!: WM3 support is achieved with integer values passed directly to the host rather than text values passed via the address bar

		*************************************************** */

		//already claimed
		if (text.match(/(You('ve)? already responded)/)) statusCode=-6; 
 
		//over-limits
		// Sorry! You can't have any more of this item.
		else if (text.match(/(can(not|'t) have any(.)?more|reached the collection limit|maximum energy has been awarded)/)) statusCode=-3; 
 			
		//all-outs
		else if (text.match(/(all (.*) (have|has) been claimed|no longer available)/)) statusCode=-2; 

		//generic fails
		else if (text.match(/(can't claim this reward)/))statusCode=-1;

		//expired fail
		else if (text.match(/(this reward has expired)/))statusCode=-11;

		//server errors
		else if (text.match(/(Reward Data was missing|does not appear to be valid)/)) statusCode=-5;
		else if (text=="") statusCode=-5;

		//accepts
		else if (text.match(/(Gift Claimed Successfully|You(.just)? got)/)) statusCode=1;

		// here we actually pass back the status code
//		if (statusCode!=0) sendMessage("status="+statusCode);
		if (statusCode!=0) Sidekick.sendStatus(statusCode);
		else window.setTimeout(read,1000); //restart if nothing was found of value
		
		//alternately, if you need WM host to process a secondary link for you, then use this form
		//Sidekick.sendStatus(statusCode,link);
		//you will need to code in a portion to actually find that link for you
		//if you need help, see the PT sidekick for ideas
		
		//if you prefer to continue with only WM2 support, then you need to do this in place of Sidekick.sendStatus
		//sendMessage("status="+statusCode);
		//sendMessage will not work properly on Chrome when window.top is not of the domain facebook.com
		//sendMessage may fail if the iframe from which you call sendMessage is not the same domain as window.top
	};

	//start the script either as a docking sidekick, or as a reward reading sidekick
	if (window.location.href.startsWith('http://www.facebook.com/')) {
		dock(); //runs the dock function above to connect to the host
		Sidekick.openChannel(); //new WM 3 methods for communication to host
	} else read(); //runs the sidekick functions to determine post process accept/fail status

})(); // anonymous function wrapper end